package zclCluster;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import wikiextract.util.io.*;

//import wikiextract.util.io.DelimitedReader;

public class HACzcl1 {
	//static String dir = "";
	static String input;//= dir + "/train";
	static String htmlDir;// = "SOME_PATH/cluster";

	enum LinkType {
		SINGLE, COMPLETE, AVERAGE
	};

	static LinkType linkType = LinkType.COMPLETE;
	static int ITERATIONS = 1000;

	public static void main(String[] args) throws IOException {
		input = args[0];
		htmlDir = args[1];
		OutputStreamWriter osw0 =new OutputStreamWriter(new FileOutputStream(args[2]));
		ITERATIONS = Integer.parseInt(args[3]);
		
		//args[4] to indicate SINGLE, COMPLETE, AVERAGE
		if(args[4].equals("SINGLE")){
			linkType = LinkType.SINGLE;
		}else if(args[4].equals("AVERAGE")){
			linkType = LinkType.AVERAGE;
		}else{
			linkType = LinkType.COMPLETE;
		}
		
		Map<Integer, Integer> name2id;
		int[] id2name;
		double[][] distances;
		String[] id2readableNames;

		name2id = new HashMap<Integer, Integer>();
		{
			BufferedReader r = new BufferedReader(new FileReader(input));
			String l = null;
			while ((l = r.readLine()) != null) {
				String[] c = l.split("\t");
				int a1 = Integer.parseInt(c[0]);
				int a2 = Integer.parseInt(c[1]);
				// double s = Double.parseDouble(c[2]);
				Integer i1 = name2id.get(a1);
				if (i1 == null)
					name2id.put(a1, name2id.size());
				Integer i2 = name2id.get(a2);
				if (i2 == null)
					name2id.put(a2, name2id.size());
			}
			r.close();
		}
		id2name = new int[name2id.size()];
		{
			for (Map.Entry<Integer, Integer> e : name2id.entrySet()) {
				id2name[e.getValue()] = e.getKey();
			}
		}
		distances = new double[name2id.size()][name2id.size()];
		{
			BufferedReader r = new BufferedReader(new FileReader(input));
			String l = null;
			while ((l = r.readLine()) != null) {
				String[] c = l.split("\t");
				int a1 = Integer.parseInt(c[0]);
				int a2 = Integer.parseInt(c[1]);
				double s = Double.parseDouble(c[2]);
				Integer i1 = name2id.get(a1);
				Integer i2 = name2id.get(a2);
				distances[i1][i2] += s;
			}
			r.close();
		}
		id2readableNames = new String[id2name.length];
		{
			DelimitedReader r = new DelimitedReader(
					"atts.text.merged-good");
			String[] t = null;
			while ((t = r.read()) != null) {
				int anid = Integer.parseInt(t[0]);
				Integer id = name2id.get(anid);
				if (id != null) {
					id2readableNames[id] = t[1];
				}
			}
			r.close();
		}

		// IMPLEMENT HIERARCHICAL CLUSTERING ALGORITHM:
		// e.g. SINGLE-LINK

		// initialize: assign each to own cluster
		List<Cluster> clusters = new ArrayList<Cluster>();
		for (int i = 0; i < name2id.size(); i++)
			clusters.add(new Cluster(i));

		for (int iteration = 0; iteration < ITERATIONS; iteration++) {
			
			double []near = new double[1];
			int[] closest = findmin(clusters, distances,near);
			System.out.println(near[0]);
			osw0.write(near[0]+"\n");
			osw0.flush();
			Cluster c = new Cluster(clusters.get(closest[0]), clusters
					.get(closest[1]));
			clusters.remove(closest[1]);
			clusters.remove(closest[0]);
			clusters.add(c);
		}
		// sort clusters by size
		Collections.sort(clusters, new Comparator<Cluster>() {
			public int compare(Cluster c1, Cluster c2) {
				return c2.count - c1.count;
			}
		});

		// print clusters of size > 1
		for (int i = 0; i < clusters.size(); i++) {
			Cluster c = clusters.get(i);
			if (c.count > 1) {
				System.out.println("CLUSTER " + i);
				List<Integer> els = c.points();
				for (Integer j : els) {
					System.out.println(" " + id2name[j] + "\t"
							+ id2readableNames[j]);
				}
				System.out.println();
			}
		}

		generateHtml(clusters, id2name, id2readableNames);
	}

	enum ClusterType {
		INTERNAL, TERMINAL
	};

	static class Cluster {
		ClusterType type;
		Cluster parent;
		Cluster child1, child2;
		int point;
		int count;

		Cluster(Cluster child1, Cluster child2) {
			this.type = ClusterType.INTERNAL;
			this.child1 = child1;
			this.child2 = child2;
			this.child1.parent = this;
			this.child2.parent = this;
			this.count = this.child1.count + this.child2.count;
		}

		Cluster(int point) {
			this.type = ClusterType.TERMINAL;
			this.point = point;
			this.count = 1;
		}

		List<Integer> points() {
			List<Integer> pts = new ArrayList<Integer>();
			points(pts);
			return pts;
		}

		void points(List<Integer> pts) {
			if (type == ClusterType.TERMINAL)
				pts.add(point);
			else if (type == ClusterType.INTERNAL) {
				child1.points(pts);
				child2.points(pts);
			}
		}
	}

	static double similaritySingle(Cluster c1, Cluster c2, double[][] dist) {
		List<Integer> pts1 = c1.points();
		List<Integer> pts2 = c2.points();

		double max = 0;
		for (int i = 0; i < pts1.size(); i++) {
			int v1 = pts1.get(i);
			for (int j = 0; j < pts2.size(); j++) {
				int v2 = pts2.get(j);
				double d = dist[v1][v2] + dist[v2][v1];
				if (d > max)
					max = d;
			}
		}
		return max;
	}

	static double similarityComplete(Cluster c1, Cluster c2, double[][] dist) {
		List<Integer> pts1 = c1.points();
		List<Integer> pts2 = c2.points();

		double min = Double.MAX_VALUE;
		for (int i = 0; i < pts1.size(); i++) {
			int v1 = pts1.get(i);
			for (int j = 0; j < pts2.size(); j++) {
				int v2 = pts2.get(j);
				double d = dist[v1][v2] + dist[v2][v1];
				if (d < min)
					min = d;
			}
		}
		return min;
	}

	static double similarityAvg(Cluster c1, Cluster c2, double[][] dist) {
		List<Integer> pts1 = c1.points();
		List<Integer> pts2 = c2.points();

		double sum = 0;
		for (int i = 0; i < pts1.size(); i++) {
			int v1 = pts1.get(i);
			for (int j = 0; j < pts2.size(); j++) {
				int v2 = pts2.get(j);
				double d = dist[v1][v2] + dist[v2][v1];
				sum += d;
			}
		}
		return sum / (pts1.size() * pts2.size());
	}

	//neardis[0]: nearest distance 
	static int[] findmin(List<Cluster> clusters, double[][] dist, double []neardis) {
		double d = 0;
		int[] pair = new int[2];
		for (int i = 0; i < clusters.size(); i++) {
			for (int j = i + 1; j < clusters.size(); j++) {
				double e = 0;
				if (linkType == LinkType.AVERAGE)
					e = similarityAvg(clusters.get(i), clusters.get(j), dist);
				else if (linkType == LinkType.SINGLE)
					e = similaritySingle(clusters.get(i), clusters.get(j), dist);
				else if (linkType == LinkType.COMPLETE)
					e = similarityComplete(clusters.get(i), clusters.get(j),
							dist);
				if (e > d) {
					d = e;
					pair[0] = i;
					pair[1] = j;
				}
			}
		}
		neardis[0] = d;
		return pair;
	}

	static void generateHtml(List<Cluster> clusters, int[] id2name,
			String[] id2readableNames) throws IOException {
		BufferedWriter w = new BufferedWriter(new OutputStreamWriter(
				new FileOutputStream(htmlDir + "/page_" + linkType.name() + "_"
						+ ITERATIONS + ".html"), "utf-8"));
		w.write("<html><body>");
		w.write("<table>");

		for (int i = 0; i < clusters.size(); i++) {
			Cluster c = clusters.get(i);
			if (c.count > 1) {
				w.write("<tr><td bgcolor='#CCCCCC' colspan=2>Cluster " + i
						+ "</td><tr>");
				List<Integer> els = c.points();
				for (Integer j : els) {
					w
							.write("<tr><td><a href='/exp/attribute.jsp?dataset=infobox&anid="
									+ id2name[j]
									+ "'>"
									+ id2name[j]
									+ "</a></td>"
									+ "<td>"
									+ id2readableNames[j] + "</td></tr>");
				}
				System.out.println();
			}
		}
		w.write("</table>");
		w.write("</body></html>");
		w.close();
	}
}